PolarDB PostgreSQL版:ePQ架构详解

PolarDB PostgreSQL版实现了弹性跨机并行查询(ePQ)特性,能够帮助您解决原先的PolarDB PostgreSQL版在处理复杂的AP查询时会遇到的问题。

前提条件

支持的PolarDB PostgreSQL版的版本如下:

PostgreSQL 11(内核小版本1.1.28及以上)

说明

您可通过如下语句查看PolarDB PostgreSQL版的内核小版本的版本号:

show polar_version;

背景信息

很多PolarDB PostgreSQL版的用户都有TP(Transactional Processing)和AP(Analytical Processing)共用的需求。他们期望数据库在白天处理高并发的TP请求,在夜间TP流量下降、机器负载空闲时进行AP的报表分析。但是即使这样,依然没有最大化利用空闲机器的资源。原先的PolarDB PostgreSQL版在处理复杂的AP查询时会遇到两大挑战:

  • 单条SQL在原生PostgreSQL执行引擎下只能在单个节点上执行,无论是单机串行还是单机并行,都无法利用其他节点的CPU、内存等计算资源,只能纵向Scale Up,不能横向Scale Out。

  • PolarDB PostgreSQL版底层是存储池,理论上I/O吞吐是无限大的。而单条SQL在原生PostgreSQL执行引擎下只能在单个节点上执行,受限于单节点CPU和内存的瓶颈,无法充分发挥存储侧大I/O带宽的优势。

如图所示:挑战

为了解决用户实际使用中的痛点,PolarDB PostgreSQL版实现了ePQ特性。当前业界HTAP的解决方案主要有以下三种:

  • TP和AP在存储和计算上完全分离。

    • 优势:两种业务负载互不影响。

    • 劣势:

      • 时效性:TP的数据需要导入到AP系统中,存在一定的延迟。

      • 成本/运维难度:增加了一套冗余的AP系统。

  • TP和AP在存储和计算上完全共享。

    • 优势:成本最小化、资源利用最大化。

    • 劣势:

      • 计算共享会导致AP查询和TP查询同时运行时会存在相互影响。

      • 扩展计算节点存储时,数据需要重分布,无法快速弹性Scale Out。

  • TP和AP在存储上共享,在计算上分离。

    说明

    本身的存储计算分离架构天然支持此方案。

HTAP特性原理

  • 架构特性。

    基于PolarDB PostgreSQL版的存储计算分离架构,推出了分布式ePQ执行引擎,提供了跨机并行执行、弹性计算、弹性扩展的保证,使得PolarDB PostgreSQL版初步具备了HTAP的能力。其优势如下:

    • 一体化存储:毫秒级数据新鲜度。

      TP/AP共享一套存储数据,减少存储成本,提高查询时效。

    • TP/AP物理隔离:杜绝CPU/内存的相互影响。

      • 单机执行引擎:在RW/RO节点,处理高并发的TP查询。

      • 分布式ePQ执行引擎:在RO节点,处理高复杂度的AP查询。

    • Serverless弹性扩展:任何一个RO节点均可发起ePQ查询。

      • Scale Out:弹性调整ePQ的执行节点范围。

      • Scale Up:弹性调整ePQ的单机并行度。

    • 消除数据倾斜、计算倾斜,充分考虑PolarDB PostgreSQL版的Buffer Pool亲和性。

    如图所示:优势

  • 分布式ePQ执行引擎。

    PolarDB PostgreSQL版ePQ的核心是分布式ePQ执行引擎,是典型的火山模型引擎。A、B两张表先做join再做聚合输出,这也是PostgreSQL单机执行引擎的执行流程。执行流程如下所示:流程

    在传统的mpp执行引擎中,数据被打散到不同的节点上,不同节点上的数据可能具有不同的分布属性,例如哈希分布、随机分布、复制分布等。传统的mpp执行引擎会针对不同表的数据分布特点,在执行计划中插入算子来保证上层算子对数据的分布属性无感知。

    不同的是,PolarDB PostgreSQL版是共享存储架构,存储上的数据可以被所有计算节点全量访问。如果使用传统的mpp执行引擎,每个计算节点Worker都会扫描全量数据,从而得到重复的数据。同时,也没有起到扫描时分治加速的效果,并不能称得上是真正意义上的mpp引擎。

    因此,在分布式ePQ执行引擎中,我们借鉴了火山模型论文中的思想,对所有扫描算子进行并发处理,引入了PxScan算子来屏蔽共享存储。PxScan算子将shared-storage的数据映射为shared-nothing的数据,通过Worker之间的协调,将目标表划分为多个虚拟分区数据块,每个Worker扫描各自的虚拟分区数据块,从而实现了跨机分布式并行扫描。

    PxScan算子扫描出来的数据会通过Shuffle算子来重分布。重分布后的数据在每个Worker上如同单机执行一样,按照火山模型来执行。

  • Serverless弹性扩展。

    传统mpp只能在指定节点发起mpp查询,因此每个节点上都只能有单个Worker扫描一张表。为了支持云原生下Serverless弹性扩展的需求,我们引入了分布式事务一致性保障。如图所示:分布式事务一致性保障

    任意选择一个节点作为Coordinator节点,它的ReadLSN会作为约定的LSN,从所有ePQ节点的快照版本号中选择最小的版本号作为全局约定的快照版本号。通过LSN的回放等待和Global Snapshot同步机制,确保在任何一个节点发起ePQ查询时,数据和快照均能达到一致可用的状态。如图所示:全链路无状态

    为了实现Serverless的弹性扩展,PolarDB PostgreSQL版从共享存储的特点出发,将Coordinator节点全链路上各个模块需要的外部依赖全部放至共享存储上。各个Worker节点运行时需要的参数也会通过控制链路从Coordinator节点同步过来,从而使Coordinator节点和Worker节点全链路无状态化(Stateless)。

    基于以上两点设计,PolarDB PostgreSQL版的弹性扩展具备了以下几大优势:

    • 任何节点都可以成为Coordinator节点,解决了传统mpp数据库Coordinator节点的单点问题。

    • PolarDB PostgreSQL版可以横向Scale Out(计算节点数量),也可以纵向Scale Up(单节点并行度),且弹性扩展即时生效,不需要重新分布数据。

    • 允许业务有更多的弹性调度策略,不同的业务域可以运行在不同的节点集合上。业务域1的SQL可以选择RO1和RO2节点来执行AP查询,业务域2的SQL可以选择使用RO3和RO4节点来执行AP查询,两个业务域使用的计算节点可以实现弹性调度。业务域

  • 消除倾斜。

    倾斜是传统mpp固有的问题,其根本原因主要是数据分布倾斜和数据计算倾斜。

    • 数据分布倾斜通常由数据打散不均衡导致,在PostgreSQL中还会由于大对象Toast表存储引入一些不可避免的数据分布不均衡问题。

    • 计算倾斜通常由于不同节点上并发的事务、Buffer Pool、网络、I/O抖动导致。

    倾斜会导致传统mpp在执行时出现木桶效应,执行完成时间受制于执行最慢的子任务。

    PolarDB PostgreSQL版设计并实现了自适应扫描机制。如下图所示,采用Coordinator节点来协调Worker节点的工作模式。在扫描数据时,Coordinator节点会在内存中创建一个任务管理器,根据扫描任务对Worker节点进行调度。Coordinator节点内部分为两个线程。

    • Data线程主要负责服务数据链路、收集汇总元组。

    • Control线程负责服务控制链路、控制每一个扫描算子的扫描进度。

    执行过程

    扫描进度较快的Worker能够扫描多个数据块。如上图中RO1与RO3的Worker各自扫描了4个数据块, RO2由于计算倾斜可以扫描更多数据块,因此它最终扫描了6个数据块。

    PolarDB PostgreSQL版ePQ的自适应扫描机制还充分考虑了PostgreSQL的Buffer Pool亲和性,保证每个Worker尽可能扫描固定的数据块,从而最大化命中Buffer Pool的概率,降低I/O开销。

TPC-H性能对比

  • 单机并行与分布式ePQ对比。

    使用256 GB内存的16个PolarDB PostgreSQL版集群作为RO节点,搭建1 TB的TPC-H环境进行对比测试。相较于单机并行,分布式ePQ并行充分利用了所有RO节点的计算资源和底层共享存储的I/O带宽,从根本上解决了前文提及的HTAP诸多挑战。在TPC-H的22条SQL中,有3条SQL加速了60多倍,19条SQL加速了10多倍,平均加速23倍。如图所示:TPC-H

    测试弹性扩展计算资源带来的性能变化。通过增加CPU的总核心数,从16核增加到128核,TPC-H的总运行时间线性提升,每条SQL的执行速度也呈线性提升,这也验证了PolarDB PostgreSQL版ePQ Serverless弹性扩展的特点。如图所示:增加CPU和总时间关系

    在测试中发现,当CPU的总核数增加到256核时,性能提升不再明显。原因是此时PolarDB PostgreSQL版共享存储的I/O带宽已经达到100%,成为了瓶颈。

  • PolarDB与传统mpp数据库对比。

    同样使用256 GB内存的16个节点,将PolarDB PostgreSQL版的分布式ePQ执行引擎与传统数据库的mpp执行引擎进行对比。

    在1 TB的TPC-H数据上,当保持与传统mpp数据库相同单机并行度的情况下(多机单进程),PolarDB PostgreSQL版的性能是传统mpp数据库的90%。其中最本质的原因是传统mpp数据库的数据默认是哈希分布的,当两张表的join key是各自的分布键时,可以不用shuffle直接进行本地的Wise Join。而PolarDB PostgreSQL版的底层是共享存储池,PxScan算子并行扫描出来的数据等价于随机分布,必须进行shuffle重分布以后才能像传统mpp数据库一样进行后续的处理。因此,TPC-H涉及到表连接时,PolarDB PostgreSQL版相比传统mpp数据库多了一次网络shuffle的开销。如图所示:TPCH执行总时间对比TPCH执行时间对比

    PolarDB PostgreSQL版分布式ePQ执行引擎能够进行弹性扩展,数据无需重分布。因此,在有限的16台机器上执行ePQ时,PolarDB PostgreSQL版还可以继续扩展单机并行度,充分利用每台机器的资源。当PolarDB PostgreSQL版的单机并行度为8时,它的性能是传统mpp数据库的5-6倍;当PolarDB PostgreSQL版的单机并行度呈线性增加时,PolarDB PostgreSQL版的总体性能也呈线性增加。只需要修改配置参数,就可以即时生效。

功能特性

  • Parallel Query并行查询。

    经过持续迭代的研发,目前PolarDB PostgreSQL版ePQ在Parallel Query上支持的功能特性主要有五大部分:

    • 基础算子全支持:扫描/连接/聚合/子查询等算子。

    • 共享存储算子优化:包括Shuffle算子共享、SharedSeqScan共享、SharedIndexScan算子等。其中SharedSeqScan共享、SharedIndexScan共享是指,在大表join小表时,小表采用类似于复制表的机制来减少广播开销,进而提升性能。

    • 分区表支持:不仅包括对Hash/Range/List三种分区方式的完整支持,还包括对多级分区静态裁剪、分区动态裁剪的支持。除此之外,PolarDB PostgreSQL版分布式ePQ执行引擎还支持分区表的Partition Wise Join。

    • 并行度弹性控制:包括全局级别、表级别、会话级别、查询级别的并行度控制。

    • Serverless弹性扩展:不仅包括任意节点发起ePQ、ePQ节点范围内的任意组合,还包括集群拓扑信息的自动维护以及支持共享存储模式、主备库模式和三节点模式。

  • Parallel DML。

    基于PolarDB PostgreSQL版读写分离架构和ePQ Serverless弹性扩展的设计, PolarDB PostgreSQL版Parallel DML支持一写多读、多写多读两种特性。

    • 一写多读:在RO节点上有多个读Worker,在RW节点上只有一个写Worker。

    • 多写多读:在RO节点上有多个读Worker,在RW节点上也有多个写Worker。多写多读场景下,读写的并发度完全解耦。

    不同的特性适用不同的场景,用户可以根据自己的业务特点来选择不同的Parallel DML功能特性。

  • 索引构建加速。

    PolarDB PostgreSQL版分布式ePQ执行引擎,不仅可以用于只读查询和DML,还可以用于索引构建加速。OLTP业务中有大量的索引,而B-Tree索引创建的过程大约有80%的时间消耗在排序和构建索引页上,20%消耗在写入索引页上。如下图所示:RO节点PolarDB PostgreSQL版利用RO节点对数据进行分布式ePQ加速排序,采用流水化的技术来构建索引页,同时使用批量写入技术来提升索引页的写入速度。

    说明

    在目前索引构建加速这一特性中,PolarDB PostgreSQL版已经支持了B-Tree索引的普通创建以及B-Tree索引的在线创建(Concurrently)两种功能。

使用指南

PolarDB PostgreSQL版ePQ适用于日常业务中的轻分析类业务,例如:对账业务,报表业务。

  • 使用ePQ进行分析型查询。

    PolarDB PostgreSQL版默认不开启ePQ功能。若您需要使用此功能,请使用如下参数:

    参数

    说明

    polar_enable_px

    指定是否开启ePQ功能。

    • on:开启ePQ功能。

    • off:(默认值)不开启ePQ功能。

    polar_px_max_workers_number

    设置单个节点上的最大ePQ Worker进程数,默认为30,取值范围为0~2147483647。该参数限制了单个节点上的最大并行度。

    说明

    节点上所有会话的ePQ workers进程数不能超过该参数大小。

    polar_px_dop_per_node

    设置当前会话并行查询的并行度,默认为1,推荐值为当前CPU 总核数,取值范围为1~128。若设置该参数为N,则一个会话在每个节点上将会启用N个ePQ Worker进程,用于处理当前的ePQ逻辑。

    polar_px_nodes

    指定参与ePQ的只读节点。默认为空,表示所有只读节点都参与。可配置为指定节点参与ePQ,以逗号分隔。

    px_worker

    指定ePQ是否对特定表生效。默认不生效。ePQ功能比较消耗集群计算节点的资源,因此只有对设置了px_workers的表才使用该功能。例如:

    • ALTER TABLE t1 SET(px_workers=1):表示t1表允许ePQ。

    • ALTER TABLE t1 SET(px_workers=-1):表示t1表禁止ePQ。

    • ALTER TABLE t1 SET(px_workers=0):表示t1表忽略ePQ(默认状态)。

    以简单的单表查询操作,来描述ePQ的功能是否有效。

    1. 创建test表并插入基础数据。

      CREATE TABLE test(id int);
      INSERT INTO test SELECT generate_series(1,1000000);
    2. 查询执行计划。

      EXPLAIN SELECT * FROM test;

      执行结果如下:

                             QUERY PLAN
      --------------------------------------------------------
       Seq Scan on test  (cost=0.00..35.50 rows=2550 width=4)
      (1 row)
      说明

      默认情况下ePQ功能不开启,单表查询执行计划为PG原生的Seq Scan。

    3. 开启并使用ePQ功能。

      ALTER TABLE test SET (px_workers=1);
      SET polar_enable_px = on;
      EXPLAIN SELECT * FROM test;

      结果如下:

                                        QUERY PLAN
      -------------------------------------------------------------------------------
       PX Coordinator 2:1  (slice1; segments: 2)  (cost=0.00..431.00 rows=1 width=4)
         ->  Seq Scan on test (scan partial)  (cost=0.00..431.00 rows=1 width=4)
       Optimizer: PolarDB PX Optimizer
      (3 rows)
    4. 配置参与ePQ的计算节点范围。

      1. 查询当前所有只读节点的名称。

        CREATE EXTENSION polar_monitor;
        
        SELECT name,host,port FROM polar_cluster_info WHERE px_node='t';

        结果如下:

         name  |   host    | port
        -------+-----------+------
         node1 | 127.0.0.1 | 5433
         node2 | 127.0.0.1 | 5434
        (2 rows)
        说明

        当前集群有2个只读节点,名称分别为:node1,node2。

      2. 指定node1只读节点参与ePQ。

        SET polar_px_nodes = 'node1';
      3. 查询参与并行查询的节点。

        SHOW polar_px_nodes;

        结果如下:

         polar_px_nodes
        ----------------
         node1
        (1 row)
        说明

        参与并行查询的节点是node1。

      4. 查询test表中所有数据。

        EXPLAIN SELECT * FROM test;

        结果如下:

                                          QUERY PLAN
        -------------------------------------------------------------------------------
         PX Coordinator 1:1  (slice1; segments: 1)  (cost=0.00..431.00 rows=1 width=4)
           ->  Partial Seq Scan on test  (cost=0.00..431.00 rows=1 width=4)
         Optimizer: PolarDB PX Optimizer
        (3 rows)                                  QUERY PLAN
                                                
  • 使用ePQ进行分区表查询。

    1. 开启ePQ功能。

      SET polar_enable_px = ON;
    2. 开启分区表ePQ功能。

      SET polar_px_enable_partition = true;
      说明

      分区表ePQ功能默认关闭。

    3. 开启多级分区表ePQ功能。

      SET polar_px_optimizer_multilevel_partitioning = true;

    当前ePQ对分区表支持的功能如下所示:

    • 支持Range分区的并行查询。

    • 支持List分区的并行查询。

    • 支持单列Hash分区的并行查询。

    • 支持分区裁剪。

    • 支持带有索引的分区表并行查询。

    • 支持分区表连接查询。

    • 支持多级分区的并行查询。

  • 使用ePQ加速索引创建。

    1. 开启使用ePQ加速创建索引功能。

      SET polar_px_enable_btbuild = on;
    2. 创建索引。

      CREATE INDEX t ON test(id) WITH(px_build = ON);
    3. 查询表结构。

      \d test

      结果如下:

                     Table "public.test"
       Column |  Type   | Collation | Nullable | Default
      --------+---------+-----------+----------+---------
       id     | integer |           |          |
       id2    | integer |           |          |
      Indexes:
          "t" btree (id) WITH (px_build=finish)
    说明

    当前仅支持对B-Tree索引的构建,且暂不支持INCLUDE等索引构建语法,暂不支持表达式等索引列类型。

    如果需要使用ePQ功能加速创建索引,请使用如下参数:

    参数

    说明

    polar_px_dop_per_node

    指定通过ePQ加速构建索引的并行度。默认为1,取值范围为1~128。

    polar_px_enable_replay_wait

    当使用ePQ加速索引构建时,当前会话内无需手动开启该参数,该参数将自动生效。以保证最近更新的数据表项可以被创建到索引中,保证索引表的完整性。索引创建完成后,该参数将会被重置为数据库默认值。

    polar_px_enable_btbuild

    是否开启使用ePQ加速创建索引。取值为OFF时不开启(默认),取值为ON时开启。

    polar_bt_write_page_buffer_size

    指定索引构建过程中的写I/O策略。该参数默认值为0(不开启),单位为块,最大值可设置为8192。推荐设置为4096。

    • 参数值为0:该参数设置为不开启,在索引创建的过程中,对于索引页写满后的写盘方式是block-by-block的单个块写盘。

    • 参数值不为0:该参数设置为开启,内核中将缓存一个polar_bt_write_page_buffer_size大小的buffer,对于需要写盘的索引页,会通过该buffer进行I/O合并再统一写盘,避免了频繁调度I/O带来的性能开销。该参数会额外提升20%的索引创建性能。